/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#if UNITY_2018_2_OR_NEWER
#define EXPOSES_SPRITE_ATLAS_UTILITIES
#endif
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;
#if UNITY_EDITOR
using UnityEditor;
using System.Reflection;
#endif
namespace Spine.Unity {
/// Loads and stores a Spine atlas and list of materials.
[CreateAssetMenu(fileName = "New Spine SpriteAtlas Asset", menuName = "Spine/Spine SpriteAtlas Asset")]
public class SpineSpriteAtlasAsset : AtlasAssetBase {
public SpriteAtlas spriteAtlasFile;
public Material[] materials;
protected Atlas atlas;
public bool updateRegionsInPlayMode;
[System.Serializable]
protected class SavedRegionInfo {
public float x, y, width, height;
public SpritePackingRotation packingRotation;
}
[SerializeField] protected SavedRegionInfo[] savedRegions;
public override bool IsLoaded { get { return this.atlas != null; } }
public override IEnumerable Materials { get { return materials; } }
public override int MaterialCount { get { return materials == null ? 0 : materials.Length; } }
public override Material PrimaryMaterial { get { return materials[0]; } }
#if UNITY_EDITOR
static MethodInfo GetPackedSpritesMethod, GetPreviewTexturesMethod;
#if !EXPOSES_SPRITE_ATLAS_UTILITIES
static MethodInfo PackAtlasesMethod;
#endif
#endif
#region Runtime Instantiation
///
/// Creates a runtime AtlasAsset
public static SpineSpriteAtlasAsset CreateRuntimeInstance (SpriteAtlas spriteAtlasFile, Material[] materials, bool initialize) {
SpineSpriteAtlasAsset atlasAsset = ScriptableObject.CreateInstance();
atlasAsset.Reset();
atlasAsset.spriteAtlasFile = spriteAtlasFile;
atlasAsset.materials = materials;
if (initialize)
atlasAsset.GetAtlas();
return atlasAsset;
}
#endregion
void Reset () {
Clear();
}
public override void Clear () {
atlas = null;
}
/// The atlas or null if it could not be loaded.
public override Atlas GetAtlas (bool onlyMetaData = false) {
if (spriteAtlasFile == null) {
Debug.LogError("SpriteAtlas file not set for SpineSpriteAtlasAsset: " + name, this);
Clear();
return null;
}
if (!onlyMetaData && (materials == null || materials.Length == 0)) {
Debug.LogError("Materials not set for SpineSpriteAtlasAsset: " + name, this);
Clear();
return null;
}
if (atlas != null) return atlas;
try {
atlas = LoadAtlas(spriteAtlasFile);
return atlas;
} catch (Exception ex) {
Debug.LogError("Error analyzing SpriteAtlas for SpineSpriteAtlasAsset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
return null;
}
}
protected void AssignRegionsFromSavedRegions (Sprite[] sprites, Atlas usedAtlas) {
if (savedRegions == null || savedRegions.Length != sprites.Length)
return;
int i = 0;
foreach (AtlasRegion region in usedAtlas) {
SavedRegionInfo savedRegion = savedRegions[i];
AtlasPage page = region.page;
region.degrees = savedRegion.packingRotation == SpritePackingRotation.None ? 0 : 90;
float x = savedRegion.x;
float y = savedRegion.y;
float width = savedRegion.width;
float height = savedRegion.height;
region.u = x / (float)page.width;
region.v = y / (float)page.height;
if (region.degrees == 90) {
region.u2 = (x + height) / (float)page.width;
region.v2 = (y + width) / (float)page.height;
} else {
region.u2 = (x + width) / (float)page.width;
region.v2 = (y + height) / (float)page.height;
}
region.x = (int)x;
region.y = (int)y;
region.width = Math.Abs((int)width);
region.height = Math.Abs((int)height);
// flip upside down
float temp = region.v;
region.v = region.v2;
region.v2 = temp;
region.originalWidth = (int)width;
region.originalHeight = (int)height;
// note: currently sprite pivot offsets are ignored.
// Sprite sprite = sprites[i];
region.offsetX = 0;//sprite.pivot.x;
region.offsetY = 0;//sprite.pivot.y;
++i;
}
}
private Atlas LoadAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
List pages = new List();
List regions = new List();
Sprite[] sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount];
spriteAtlas.GetSprites(sprites);
if (sprites.Length == 0)
return new Atlas(pages, regions);
Texture2D texture = null;
#if UNITY_EDITOR
if (!Application.isPlaying)
texture = AccessPackedTextureEditor(spriteAtlas);
else
#endif
texture = AccessPackedTexture(sprites);
Material material = materials[0];
#if !UNITY_EDITOR
material.mainTexture = texture;
#endif
Spine.AtlasPage page = new AtlasPage();
page.name = spriteAtlas.name;
page.width = texture.width;
page.height = texture.height;
page.format = Spine.Format.RGBA8888;
page.minFilter = TextureFilter.Linear;
page.magFilter = TextureFilter.Linear;
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
page.rendererObject = material;
pages.Add(page);
sprites = AccessPackedSprites(spriteAtlas);
int i = 0;
for (; i < sprites.Length; ++i) {
Sprite sprite = sprites[i];
AtlasRegion region = new AtlasRegion();
region.name = sprite.name.Replace("(Clone)", "");
region.page = page;
region.degrees = sprite.packingRotation == SpritePackingRotation.None ? 0 : 90;
region.u2 = 1;
region.v2 = 1;
region.width = page.width;
region.height = page.height;
region.originalWidth = page.width;
region.originalHeight = page.height;
region.index = i;
regions.Add(region);
}
Atlas atlas = new Atlas(pages, regions);
AssignRegionsFromSavedRegions(sprites, atlas);
return atlas;
}
#if UNITY_EDITOR
public static void UpdateByStartingEditorPlayMode () {
EditorApplication.isPlaying = true;
}
public static bool AnySpriteAtlasNeedsRegionsLoaded () {
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset");
foreach (string guid in guids) {
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path)) {
SpineSpriteAtlasAsset atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path);
if (atlasAsset) {
if (atlasAsset.RegionsNeedLoading)
return true;
}
}
}
return false;
}
public static void UpdateWhenEditorPlayModeStarted () {
if (!EditorApplication.isPlaying)
return;
EditorApplication.update -= UpdateWhenEditorPlayModeStarted;
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset");
if (guids.Length == 0)
return;
Debug.Log("Updating SpineSpriteAtlasAssets");
foreach (string guid in guids) {
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path)) {
SpineSpriteAtlasAsset atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(path);
if (atlasAsset) {
atlasAsset.atlas = atlasAsset.LoadAtlas(atlasAsset.spriteAtlasFile);
atlasAsset.LoadRegionsInEditorPlayMode();
Debug.Log(string.Format("Updated regions of '{0}'", atlasAsset.name), atlasAsset);
}
}
}
EditorApplication.isPlaying = false;
}
public bool RegionsNeedLoading {
get { return savedRegions == null || savedRegions.Length == 0 || updateRegionsInPlayMode; }
}
public void LoadRegionsInEditorPlayMode () {
Sprite[] sprites = null;
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
MethodInfo method = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
if (method != null) {
object retval = method.Invoke(null, new object[] { spriteAtlasFile });
Sprite[] spritesArray = retval as Sprite[];
if (spritesArray != null && spritesArray.Length > 0) {
sprites = spritesArray;
}
}
if (sprites == null) {
sprites = new UnityEngine.Sprite[spriteAtlasFile.spriteCount];
spriteAtlasFile.GetSprites(sprites);
}
if (sprites.Length == 0) {
Debug.LogWarning(string.Format("SpriteAtlas '{0}' contains no sprites. Please make sure all assigned images are set to import type 'Sprite'.", spriteAtlasFile.name), spriteAtlasFile);
return;
} else if (sprites[0].packingMode == SpritePackingMode.Tight) {
Debug.LogError(string.Format("SpriteAtlas '{0}': Tight packing is not supported. Please disable 'Tight Packing' in the SpriteAtlas Inspector.", spriteAtlasFile.name), spriteAtlasFile);
return;
}
if (savedRegions == null || savedRegions.Length != sprites.Length)
savedRegions = new SavedRegionInfo[sprites.Length];
int i = 0;
foreach (AtlasRegion region in atlas) {
Sprite sprite = sprites[i];
Rect rect = sprite.textureRect;
float x = rect.min.x;
float y = rect.min.y;
float width = rect.width;
float height = rect.height;
SavedRegionInfo savedRegion = new SavedRegionInfo();
savedRegion.x = x;
savedRegion.y = y;
savedRegion.width = width;
savedRegion.height = height;
savedRegion.packingRotation = sprite.packingRotation;
savedRegions[i] = savedRegion;
++i;
}
updateRegionsInPlayMode = false;
AssignRegionsFromSavedRegions(sprites, atlas);
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
}
public static Texture2D AccessPackedTextureEditor (SpriteAtlas spriteAtlas) {
#if EXPOSES_SPRITE_ATLAS_UTILITIES
UnityEditor.U2D.SpriteAtlasUtility.PackAtlases(new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget);
#else
/*if (PackAtlasesMethod == null) {
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasUtility,UnityEditor");
PackAtlasesMethod = T.GetMethod("PackAtlases", BindingFlags.NonPublic | BindingFlags.Static);
}
if (PackAtlasesMethod != null) {
PackAtlasesMethod.Invoke(null, new object[] { new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget });
}*/
#endif
if (GetPreviewTexturesMethod == null) {
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
GetPreviewTexturesMethod = T.GetMethod("GetPreviewTextures", BindingFlags.NonPublic | BindingFlags.Static);
}
if (GetPreviewTexturesMethod != null) {
object retval = GetPreviewTexturesMethod.Invoke(null, new object[] { spriteAtlas });
Texture2D[] textures = retval as Texture2D[];
if (textures.Length > 0)
return textures[0];
}
return null;
}
#endif
public static Texture2D AccessPackedTexture (Sprite[] sprites) {
return sprites[0].texture;
}
public static Sprite[] AccessPackedSprites (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
Sprite[] sprites = null;
#if UNITY_EDITOR
if (!Application.isPlaying) {
if (GetPackedSpritesMethod == null) {
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
GetPackedSpritesMethod = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
}
if (GetPackedSpritesMethod != null) {
object retval = GetPackedSpritesMethod.Invoke(null, new object[] { spriteAtlas });
Sprite[] spritesArray = retval as Sprite[];
if (spritesArray != null && spritesArray.Length > 0) {
sprites = spritesArray;
}
}
}
#endif
if (sprites == null) {
sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount];
spriteAtlas.GetSprites(sprites);
if (sprites.Length == 0)
return null;
}
return sprites;
}
}
}